﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WindGoes6.Utils
{
    /// <summary>
    /// 此类基于System.Process提供了多种方式用于简化DOS程序的调用。
    /// </summary>
    public class DosInvoker
    {
		/// <summary>
		/// 用于执行第三方程序，并返回执行结果，结果一次读取，要么有要么无。
		/// </summary>
		/// <param name="exepath">可执行的程序的文件路径。</param>
		/// <param name="args">程序的参数。</param>
		/// <param name="timeout">超时时间，单位秒，设置为0时不限时。</param>
		/// <returns></returns>
		public static string RunCommand(string exepath, string args, int timeout = 10)
		{
			Process p = new Process();
			p.StartInfo.FileName = exepath;
			p.StartInfo.Arguments = args;
			p.StartInfo.UseShellExecute = false; // 不显示用户界面
			p.StartInfo.RedirectStandardOutput = true; // 是否重定位输出于当前输出。
			p.StartInfo.CreateNoWindow = true; // 不创建新窗口。

			string output = "";
			try
			{
				if (p.Start())//开始进程  
				{
					if (timeout == 0)
					{
						p.WaitForExit();//这里无限等待进程结束  
					}
					else
					{
						p.WaitForExit(timeout * 1000); //等待进程结束，等待时间为指定的毫秒  
					}

					// 如果还没有结束，就将线程关闭
					if (!p.HasExited)
						p.Kill();

					output = p.StandardOutput.ReadToEnd();//读取进程的输出  
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.Message);//捕获异常，输出异常信息
			}
			finally
			{
				if (p != null)
					p.Close();
			}

			return output;
		}



		/// <summary>
		/// 推荐1：调用一个DOS程序，以行的形式返回结果，所有结果返回在一个结果类中。
		/// 在调用时，如果在指定时间内没有完成，则强制结束调用线程。
		/// </summary>
		/// <param name="exepath">待执行的DOS程序文件的路径。</param>
		/// <param name="args">参数列表。</param>
		/// <returns>返回结果为InvokeResult类型。</returns>
		public static DosInvokerResult RunCommandEx(string exepath, params string[] args)
		{
			if (File.Exists(exepath))
				throw new FileNotFoundException("未找到指定的程序。", exepath);

			for (int i = 0; i < args.Length; i++)
			{
				if (args[i] == null)
					throw new ArgumentNullException("参数不能为null。");
				if (args[i].Contains(' '))
					args[i] = "\"" + args[i] + "\"";
			}

			DosInvokerResult ccr = new DosInvokerResult();
			Process p = new Process();
			p.StartInfo.FileName = exepath;
			p.StartInfo.Arguments = string.Join(" ", args);
			p.StartInfo.UseShellExecute = false; // 必需设置此属性为true，下面两个属性才有效
			p.StartInfo.RedirectStandardOutput = true; // 关键行20
			p.StartInfo.CreateNoWindow = true;
			p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
			p.OutputDataReceived += (s, e) => ccr.Lines.Add(e.Data);
			p.Start();
			DateTime dt1 = DateTime.Now;
			p.BeginOutputReadLine();
			p.WaitForExit(30 * 1000);
			DateTime dt2 = DateTime.Now;

			if (!p.HasExited)
			{
				p.Kill();
				ccr.Successful = false;
			}
			ccr.ExitCode = p.ExitCode;
			ccr.ExecutionTime = (dt2 - dt1).TotalSeconds;
			ccr.Successful = p.ExitCode == 0;
			return ccr;
		}


		/// <summary>
		/// 推荐2：调用一个DOS程序，以行的形式返回结果，所有结果返回在一个结果类中。
		/// 在调用时，如果在指定时间内没有完成，则强制结束调用线程。
		/// </summary>
		/// <param name="exepath">待执行的DOS程序文件的路径。</param>
		/// <param name="args">参数列表。</param>
		/// <param name="timeout">超时设置，单位：秒。</param>
		/// <returns>返回结果为InvokeResult类型。</returns>
		public static DosInvokerResult RunCommandEx(string exepath, string args, double timeout = 10)
		{
			DosInvokerResult ccr = new DosInvokerResult();
			Process p = new Process();
			p.StartInfo.FileName = exepath;
			p.StartInfo.Arguments = args;
			p.StartInfo.UseShellExecute = false; // 必需设置此属性为true，下面两个属性才有效
			p.StartInfo.RedirectStandardOutput = true; // 关键行20
			p.StartInfo.CreateNoWindow = true;
			p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
			p.OutputDataReceived += (s, e) => ccr.Lines.Add(e.Data);
			p.Start();
			DateTime dt1 = DateTime.Now;
			p.BeginOutputReadLine();
			p.WaitForExit((int)(timeout * 1000));
			DateTime dt2 = DateTime.Now;

			if (!p.HasExited)
			{
				p.Kill();
				ccr.Successful = false;
			}
			ccr.ExitCode = p.ExitCode;
			ccr.ExecutionTime = (dt2 - dt1).TotalSeconds;
			ccr.Successful = p.ExitCode == 0;
			return ccr;
		}

		/// <summary>
		/// 推荐3：调用一个DOS程序，以行的形式返回结果，所有结果返回在一个结果类中。
		/// 在调用时，如果在指定时间内没有完成，则强制结束调用线程。
		/// </summary>
		/// <param name="exepath">待执行的DOS程序文件的路径。</param>
		/// <param name="timeout"></param>
		/// <param name="args">参数列表。</param>
		/// <returns>返回结果为InvokeResult类型。</returns>
		public static DosInvokerResult RunCommandEx(string exepath, double timeout, params string[] args)
		{
			if (File.Exists(exepath))
				throw new FileNotFoundException("未找到指定的程序。", exepath);

			for (int i = 0; i < args.Length; i++)
			{
				if (args[i] == null)
					throw new ArgumentNullException("参数不能为null。");
				if (args[i].Contains(' '))
					args[i] = "\"" + args[i] + "\"";
			}

			DosInvokerResult ccr = new DosInvokerResult();
			Process p = new Process();
			p.StartInfo.FileName = exepath;
			p.StartInfo.Arguments = string.Join(" ", args);
			p.StartInfo.UseShellExecute = false; // 必需设置此属性为true，下面两个属性才有效
			p.StartInfo.RedirectStandardOutput = true; // 关键行20
			p.StartInfo.CreateNoWindow = true;
			p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
			p.OutputDataReceived += (s, e) => ccr.Lines.Add(e.Data);
			p.Start();
			DateTime dt1 = DateTime.Now;
			p.BeginOutputReadLine();
			p.WaitForExit((int)(timeout * 1000));
			DateTime dt2 = DateTime.Now;

			if (!p.HasExited)
			{
				p.Kill();
				ccr.Successful = false;
			}
			ccr.ExitCode = p.ExitCode;
			ccr.ExecutionTime = (dt2 - dt1).TotalSeconds;
			ccr.Successful = p.ExitCode == 0;
			return ccr;
		}

		/// <summary>
		/// 用于执行DOS【自带】的命令并返回执行结果（仅包括执行结果信息），DOS命令是是通过 cmd.exe 调用执行的。
		/// 调用格式如： RunDosCommand("dir", "*.exe);
		/// </summary>
		/// <param name="cmd">待调用的DOS命令。</param>
		/// <param name="args">命令的参数。</param>
		/// <param name="timeout">超时时间，单位为秒，默认值为30。</param>
		/// <returns></returns>
		public static string RunDosCommand(string cmd, string args = "", double timeout = 10)
		{
			Process p = new Process();
			p.StartInfo.FileName = "cmd.exe";
			p.StartInfo.Arguments = $"/C {cmd} {args}";
			p.StartInfo.UseShellExecute = false; // 不显示用户界面
			p.StartInfo.RedirectStandardOutput = true; // 是否重
			p.StartInfo.CreateNoWindow = true;

			// 返回数据
			string output;

			if (p.Start())//开始进程  
			{
				p.WaitForExit((int)(timeout * 1000)); //等待进程结束，等待时间为指定的毫秒  

				if (p.HasExited)
				{
					output = p.StandardOutput.ReadToEnd();//读取进程的输出  

				}
				else
				{
					p.Kill();
					output = "Interrupted";
				}

				p.Close();
			}
			else
			{
				output = "failed";
			}



			return output;
		}


		#region 以下代码未公开，因为暂时没经过测试。
		/// <summary>
		/// 用于执行【DOS自带】的命令并返回执行结果（仅包括执行结果信息），DOS命令是是通过 cmd.exe 调用执行的。
		/// 由于绝大部分情况下DOS程序的执行不会卡死，所以没有设置等待时间。
		/// 调用格式如： RunDosCommand("dir", "*.exe);
		/// </summary>
		/// <param name="doscmd">待调用的DOS命令。</param>
		/// <param name="arguments">参数列表</param>
		/// <returns></returns>
		static string RunDosCommand(string doscmd, params string[] arguments)
        {
            Process p = new Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.Arguments = "/C " + doscmd + " " + string.Join(" ", arguments);
            p.StartInfo.UseShellExecute = false; // 不显示用户界面
            p.StartInfo.RedirectStandardOutput = true; // 是否重
            p.StartInfo.CreateNoWindow = true;
            // 返回数据
            string output = "";
            try
            {
                if (p.Start())//开始进程  
                {
                    p.WaitForExit(); //等待进程结束，等待时间为指定的毫秒  
                    output = p.StandardOutput.ReadToEnd();//读取进程的输出  
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);//捕获异常，输出异常信息
            }
            finally
            {
                if (p != null)
                    p.Close();
            }

            return output;
        }

		/// <summary>
		/// 向指定的程序注入需要执行的程序。
		/// </summary>
		/// <param name="execuable"></param>
		/// <param name="cmds"></param>
		/// <returns></returns>
		static string RunCommands(string execuable, params string[] cmds)
        {
            Process p = new Process();
            p.StartInfo.FileName = execuable;
            p.StartInfo.Arguments = string.Join(" ", cmds);
            p.StartInfo.UseShellExecute = false; // 不显示用户界面
            p.StartInfo.RedirectStandardOutput = true; // 是否重定位输出于当前输出。
            p.StartInfo.CreateNoWindow = true; // 不创建新窗口。
            p.StartInfo.RedirectStandardInput = true;

            string output = "";
            try
            {
                if (p.Start())//开始进程  
                {
                    for (int i = 0; i < cmds.Length; i++)
                    {
                        p.StandardInput.WriteLine(cmds[i]);
                        Console.WriteLine(p.StandardOutput.ReadToEnd());
                    }

                    p.StandardInput.WriteLine("exit");
                    output = p.StandardOutput.ReadToEnd();//读取进程的输出  
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);//捕获异常，输出异常信息
            }
            finally
            {
                if (p != null)
                    p.Close();
            }

            return output;
        }
		#endregion

    }
}
